package ui.helpers;

import core.Change;
import core.ChangesLogger;
import core.Database;
import core.ReverseChange;
import core.SqlScriptGenerator;
import core.SqlScriptGeneratorFactory;
import java.math.BigDecimal;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.apache.log4j.Logger;
import ui.ApplicationWindow;
import ui.DbVersioning;
import ui.SqlExportPanel;

/**
 *
 * @author Sakis Vtdk <sakis.vtdk@gmail.com>
 */
public class ExportSqlListener implements ActionListener
{

	@Override
	public void actionPerformed(ActionEvent e) 
	{
		SqlExportPanel panel = new SqlExportPanel();

		int selection = JOptionPane.showConfirmDialog(null, panel, "Export Sql", JOptionPane.OK_CANCEL_OPTION);

		if(selection == JOptionPane.OK_OPTION)
		{
			//if the user wants to export only the last set of changes
			if(panel.exportLastChangesOnly())
			{
				generateCurrentSql();
			}
			else
			{
				BigDecimal baseVersion = panel.getSelectedBaseVersion();
				BigDecimal targetVersion = panel.getSelectedTargetVersion();

				if(baseVersion.compareTo(targetVersion) == 0)
				{
					String error = "The starting version and ending version cannot be the same. Try again."; 
					log.error("Error while trying to export sql: Base version and Taret version were the same.");
					JOptionPane.showMessageDialog(null, error, "Error", JOptionPane.ERROR_MESSAGE);
				}
				else if(baseVersion.compareTo(targetVersion) < 0 && !panel.generateReverseSql()) //forward script
				{
					try
					{
						generateSql(baseVersion, targetVersion);
					}
					catch(Exception ex)
					{
						String error = "Could not create the requested sql file. "; 
						log.error(error +"Error returned was: "+ ex.getMessage());
						JOptionPane.showMessageDialog(null, error, "Error", JOptionPane.ERROR_MESSAGE);
					}
				}
				else if(baseVersion.compareTo(targetVersion) < 0 && panel.generateReverseSql()) //reverse script
				{
					//first inform the user that he will be going to a previous version
					String warnMessage = "You selected to generate a script to a previous version of the database. That may lead to data loss. Continue?";
					selection = JOptionPane.showConfirmDialog(null, warnMessage, "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

					if(selection == JOptionPane.YES_OPTION)
					{
						generateReverseSql(baseVersion, targetVersion);
					}
				}
			}
			
		}
	}

	/**
	 * Generates the sql file for the changes made in the current version
	 */
	private void generateCurrentSql()
	{
		//get the current version of the database
		BigDecimal targetVersion = Database.getCurrentWorkingInstance().getCurrentVersion();

		log.info("Creating sql to move to version "+ targetVersion);

		//so we can get the changes made in this session
		ArrayList<Change> changes = ChangesLogger.getChangesLogger().getChangesFromVersion(targetVersion);

		//create the script generator and get the sql script for these changes
		SqlScriptGenerator scriptGenerator = SqlScriptGeneratorFactory.getSqlScriptGenerator(DbVersioning.DEFAULT_SQL_ENGINE);
		String sqlScript = scriptGenerator.getSqlScript(changes);

		//write it in a file
		saveToFile(sqlScript);
	}

	/**
	 * Generates the sql file for the changes made between the versions <code>baseVersion</code>
	 * and <code>targetVersion</code>
	 * 
	 * @param baseVersion The starting version
	 * @param targetVersion The ending version
	 * 
	 * @throws Exception If anything goes wrong an exception is thrown
	 */
	private void generateSql(BigDecimal baseVersion, BigDecimal targetVersion) throws Exception
	{
		Database tmpDb = null;
		String sqlScript = "";

		//the base version represents the *current* version of the user's
		//database, so we need to get the (baseVersion + versionStep) changes
		baseVersion = baseVersion.add(Database.getVersionStep());

		do
		{
			try
			{
				Database.restoreWorkingInstance();
				
				//if the database we need to load is NOT the one currently working on
				//we load it
				if(baseVersion.compareTo(Database.getDatabase().getCurrentVersion()) != 0)
				{
					tmpDb = Database.retrieveSnapShot(baseVersion);
				}
				else
				{
					tmpDb = Database.getDatabase();
				}
			}
			catch (Exception ex)
			{
				log.error(ex.getMessage());
				JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
			}

			//get the changes that happened in that version
			ArrayList<Change> changes = ChangesLogger.getChangesLogger().
					getChangesForVersion(tmpDb.getCurrentVersion());

			//create script generator and get the sql script for these changes
			SqlScriptGenerator scriptGenerator = SqlScriptGeneratorFactory.
					getSqlScriptGenerator(DbVersioning.DEFAULT_SQL_ENGINE);

			sqlScript += scriptGenerator.getSqlScript(changes);

			//and get this next version of the database
			baseVersion = tmpDb.getNextVersion();

		}while(tmpDb.getNextVersion().compareTo(targetVersion) <= 0);

		//restore the working instance
		Database.restoreWorkingInstance();

		//write it in a file
		saveToFile(sqlScript);
	}

	private void generateReverseSql(BigDecimal baseVersion, BigDecimal targetVersion)
	{
		//1. get the changes that got us from targetVersion - 1 to targetVersion
		ArrayList<Change> changes = ChangesLogger.getChangesLogger().getChangesForVersion(targetVersion);
		ArrayList<Change> reversedChanges = (new ReverseChange(changes)).getReversedChanges();
		log.trace("Original Changes || Reversed Changes: "+ changes.size() +" || "+ reversedChanges.size());

		
		//======================================================================
		//The following will be by-passed by adding the logic of getting the 
		// definitions of the columns contained within a table, inside the 
		//script generator
		//======================================================================
		//2. if a reversed change is CREATE something that was deleted
		//   fetch all the changes that modify that something (e.g. the columns 
		//   of a deleted table)
		//   until it gets to its last state
		//======================================================================

		//3. load the targetVersion and apply the reversed changes on that version
		//(now we have the script to go to targetVersion - 1)
		Database tmpDb = null;

		try
		{
			Database.restoreWorkingInstance(); 
			//tmpDb = Database.retrieveSnapShot(targetVersion.subtract(Database.getVersionStep())); //targetVersion - 0.1
			tmpDb = Database.retrieveSnapShot(targetVersion);
		}
		catch (Exception ex)
		{
			log.error(ex.getMessage());
			JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
		}
		log.trace("Creating sql...");
		SqlScriptGenerator scriptGenerator = SqlScriptGeneratorFactory.getSqlScriptGenerator(DbVersioning.DEFAULT_SQL_ENGINE);
		String sqlScript = scriptGenerator.getSqlScript(reversedChanges);

		Database.restoreWorkingInstance();
		//4. GoTo 1.

		saveToFile(sqlScript);
	}

	/**
	 * Show the fileChooser dialog and saves the given <code>sqlScript</code> to
	 * the specified file
	 * 
	 * @param sqlScript The sql script to be written in the file
	 */
	private void saveToFile(String sqlScript)
	{
		//get the fileChooser
		JFileChooser fileChooser = ApplicationWindow.getApplicationWindow().getFileChooser();

		//set the filter
		fileChooser.setCurrentDirectory(new File(DbVersioning.DEFAULT_DATA_FOLDER));
		FileFilter filter = new FileNameExtensionFilter("Sql Files", DbVersioning.SQL_FILE_EXTENSION);
		fileChooser.setFileFilter(filter);

		//show the dialog
		int choice = fileChooser.showSaveDialog(null);

		if(choice == JFileChooser.APPROVE_OPTION)
		{
			//get the file from the dialog box
			File file = fileChooser.getSelectedFile();
			String fileName = file.getName();
			String path = file.getPath().substring(0, file.getPath().indexOf(fileName));

			//check if there is an extension and if not, add it
			if(!ApplicationWindow.getApplicationWindow().fileHasExtension(file, DbVersioning.SQL_FILE_EXTENSION))
			{
				fileName = fileName +"."+ DbVersioning.SQL_FILE_EXTENSION;
				file = new File(path + fileName);
			}

			//try to save the file
			try
			{
				PrintWriter output = new PrintWriter(file);
				output.print(sqlScript);
				output.close();
				ApplicationWindow.getApplicationWindow().updateSqlTextArea(sqlScript);
			}
			catch(IOException e)
			{
				JOptionPane.showMessageDialog(null, "There was an error when saving to file", "Error", JOptionPane.ERROR_MESSAGE);
				log.error("The following error occured when trying to save the file: "+ e.getMessage());
			}
		}
	}

	// <editor-fold desc="Getters">

	// </editor-fold>


	// <editor-fold desc="Setters">

	// </editor-fold>


	// <editor-fold desc="Private variables">

	/**
	 * Logger instance
	 */
	private static Logger log = Logger.getLogger(ExportSqlListener.class);

	// </editor-fold>
}
